/*
	time_64.c

	contains functionality to get the current system time

	CMOS information gleamed from http://wiki.osdev.org/CMOS

	Author: Aidan Goddard 31/12/13
*/

#include"../kernel/headers/types.h"
#include"../kernel/headers/system_config_tables.h"
#include"headers/printf.h"

#define CMOS_REG_SELECT 0x70
#define CMOS_REG_WINDOW 0x71

#define RTC_SECONDS 0x00
#define RTC_MINUTES 0x02
#define RTC_HOURS 0x04
#define RTC_DAY 0x07
#define RTC_MONTH 0x08
#define RTC_YEAR 0x09
#define RTC_STAT_A 0x0a
#define RTC_STAT_B 0x0b

static uint32_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // for non-leap years

void SetSystemTime(void)
{
	// time portions
	uint32_t seconds, minutes, hours, days, months, years;

	// wait for RTC update to complete
	// wait for update in progress flag to be set
	uint32_t update_flag = 0;
	while(update_flag == 0)
	{
		// send the reg value to the CMOS
		outb(CMOS_REG_SELECT, RTC_STAT_A);
		io_wait();
		update_flag = inb(CMOS_REG_WINDOW) & (1 << 7);
		io_wait();
	}
	// update in progress flag is set
	// now wait for it to be cleared again
	while(update_flag == 1)
	{
		// send the reg value to the CMOS
		outb(CMOS_REG_SELECT, RTC_STAT_A);
		io_wait();
		update_flag = inb(CMOS_REG_WINDOW) & (1 << 7);
		io_wait();
	}

	// flag is cleared, now read in values
	// seconds
	outb(CMOS_REG_SELECT, RTC_SECONDS);
	io_wait();
	seconds = inb(CMOS_REG_WINDOW) & 0xff;
	io_wait();

	// minutes
	outb(CMOS_REG_SELECT, RTC_MINUTES);
	io_wait();
	minutes = inb(CMOS_REG_WINDOW) & 0xff;
	io_wait();

	// hours
	outb(CMOS_REG_SELECT, RTC_HOURS);
	io_wait();
	hours = inb(CMOS_REG_WINDOW) & 0xff;
	io_wait();

	// days
	outb(CMOS_REG_SELECT, RTC_DAY);
	io_wait();
	days = inb(CMOS_REG_WINDOW) & 0xff;
	io_wait();

	// months
	outb(CMOS_REG_SELECT, RTC_MONTH);
	io_wait();
	months = inb(CMOS_REG_WINDOW) & 0xff;
	io_wait();

	// years
	outb(CMOS_REG_SELECT, RTC_YEAR);
	io_wait();
	years = inb(CMOS_REG_WINDOW) & 0xff;
	io_wait();

	// translate the bytes into numerical values
	// find out what format it's in
	outb(CMOS_REG_SELECT, RTC_STAT_B);
	io_wait();
	uint8_t format = inb(CMOS_REG_WINDOW);
	io_wait();

	// decode the format byte
	uint32_t binary, twentyfour;
	twentyfour = format & 2;
	binary = format & 4;

	// need to convert it to binary from BCD
	if(!binary)
	{
		// seconds
		seconds = (seconds & 0x0f) + (((seconds & 0xf0) >> 4) * 10);
		minutes = (minutes & 0x0f) + (((minutes & 0xf0) >> 4) * 10);
		hours = ((hours & 0x0f) + (((hours & 0x70) >> 4) * 10)) | (hours & 0x80); // remember top bit represents if AM or PM in 12 hour mode. will be 0 for 24 hour mode
		days = (days & 0x0f) + (((days & 0xf0) >> 4) * 10);
		months = (months & 0x0f) + (((months & 0xf0) >> 4) * 10);
		years = (years & 0x0f) + (((years & 0xf0) >> 4) * 10);
	}

	// then convert to 24 hours
	if(!twentyfour)
	{
		// check if AM or PM
		if(hours & 0x80)	// test top bit
		{
			// is PM
			// mask the bit
			hours = hours & 0x7f;

			// check cases
			if(hours != 12)
			{
				// add 12 hours to them
				hours += 12;
			}
		}
		else
		{
			// is AM
			// check for 12 AM
			if(hours == 12)
			{
				hours = 0;
			}
		}
	}

	// check which century (it could happen!)
	if(years < 14)
	{
		years += 2100;
	}
	else
	{
		years += 2000;
	}

	// now need to calculate ms since jan 1 1970
	uint64_t ms_since_epoc = 0;

	// first calculate how many leap years there were between now and then
	// is a leap year if divisible by 4, but not if new century and not divisible by 400
	// do not count this year
	uint32_t c_years = years - 1970;
	uint32_t l_years = c_years / 4;

	// take into account 2100 is not a leap year
	if(c_years >= 130)
	{
		l_years -= 1;
	}

	// add the previous leap year days' worth of ms
	ms_since_epoc = 24 * 60 * 60 * 1000 * l_years;

	// add the normal years worth
	ms_since_epoc += c_years * 365ULL * 24 * 60 * 60 * 1000;

	// now add ms for the current time of day
	ms_since_epoc += (hours * 60 * 60 * 1000) + (minutes * 60 * 1000) + (seconds * 1000);

	// now need to add all the days for the months prior to this one since january of this year
	uint32_t i;
	uint64_t c_days = 0;
	for(i = 0; i < (months - 1); i++)
	{
		c_days += days_in_month[i];
	}

	// add a day if in leap year and past february
	if((months > 2) && (years % 4 == 0) && (years != 2100))
	{
		c_days += 1;
	}

	// finally add days for this month and get ms
	c_days += days - 1;
	ms_since_epoc += (c_days * 24 * 60 * 60 * 1000);

	// save it to the system table
	TBL_SYSCONFIG *cfg = GetSystemConfigBase();
	cfg->tbl_stuff.start_time = ms_since_epoc;
	cfg->tbl_stuff.time = ms_since_epoc;




	/*

	// TEST: print current time
	printf("\n\n%u:%u:%u  %u/%u/", hours, minutes, seconds, days, months);
	printf("%u", years);
	printf("\n\n%u %u %u", c_years, l_years, c_days);
	printf("\n ms since epoc %u", ms_since_epoc);
	printf("\n  s since epoc %u", ms_since_epoc / 1000);
	printf("\n  m since epoc %u", ms_since_epoc / (1000 * 60));
	printf("\n  h since epoc %u", ms_since_epoc / (1000 * 60 * 60));
	printf("\n  d since epoc %u", ms_since_epoc / (1000 * 60 * 60 * 24));
	printf("\n  y since epoc %u", ms_since_epoc / (1000 * 60 * 60 * 24 * 365ULL));
	printf("\nFORMAT: %bb", format);
	*/
	// finished
	return;
}





















